/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects.coders;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Date;

import org.latestbit.smartobjects.exceptions.DataTypesCoderException;
import org.latestbit.smartobjects.exceptions.SmartBusSystemException;
import org.latestbit.smartobjects.io.ResizableByteBuffer;

public class StdDataTypesCoder implements IDataTypesCoder {
	
	StdDataTypesCoderGrammar grammar = new StdDataTypesCoderGrammar();
	
	public enum UnicodeEncodingTypes {
		UCS_2 ( (byte) 0x01 );

		private byte value;
		
		UnicodeEncodingTypes(byte value) {
			this.value=value;
		}

		public byte getValue() {
			return value;
		}
		
	}


	private static final String STRING_ENCODING = "US-ASCII";
	private static final String UNICODE_STRING_ENCODING = "UTF-16BE";


	public enum DataTypeTags {
		NULL 					( (byte) 0),
		BOOLEAN					( (byte) 1),
		BYTE					( (byte) 2),
		UBYTE					( (byte) 3),
		INT16					( (byte) 4),
		UINT16					( (byte) 5),
		INT32					( (byte) 6),
		UINT32					( (byte) 7),
		INT64					( (byte) 8),
		UINT64					( (byte) 9),
		FLOAT					( (byte) 10),
		DOUBLE					( (byte) 11),		
		STRING					( (byte) 12),		
		WSTRING					( (byte) 13),
		CHAR					( (byte) 14),		
		WCHAR					( (byte) 15),
		DATETIME				( (byte) 16),
		BIGINTEGER				( (byte) 17),
		BIGDECIMAL				( (byte) 18),
		BYTE_ARRAY				( (byte) 50),
		UBYTE_ARRAY				( (byte) 51),
		INT16_ARRAY				( (byte) 52),
		UINT16_ARRAY			( (byte) 53),
		INT32_ARRAY				( (byte) 54),
		UINT32_ARRAY			( (byte) 55),
		INT64_ARRAY				( (byte) 56),
		UINT64_ARRAY			( (byte) 57),
		FLOAT_ARRAY				( (byte) 58),
		DOUBLE_ARRAY			( (byte) 59),
		STRING_ARRAY			( (byte) 60),
		WSTRING_ARRAY			( (byte) 61),
		CHAR_ARRAY				( (byte) 62),
		WCHAR_ARRAY				( (byte) 63),
		BOOLEAN_ARRAY			( (byte) 64),
		DATETIME_ARRAY			( (byte) 65),
		BIGINTEGER_ARRAY		( (byte) 66),
		BIGDECIMAL_ARRAY		( (byte) 67),		
		OBJECT_ARRAY			( (byte) 101),
		OBJECT_LIST		 		( (byte) 102),
		OBJECT_MAP				( (byte) 103),

		OBJECT_TYPE				( (byte) 0xEC),
		ENCRYPTED_DATA	 		( (byte) 0xED),
		NAMED_DATA_PART			( (byte) 0xEE),

		STRUCTURE_TYPE_BEGIN 	( (byte) 0xF0),
		STRUCTURE_TYPE_END 		( (byte) 0xF1),		

		EXCEPTION_TYPE_BEGIN 	( (byte) 0xF5),
		EXCEPTION_TYPE_END 		( (byte) 0xF6),		
		
		CHOICE_TYPE		 		( (byte) 0xFA),
		ENUM_TYPE		 		( (byte) 0xFC)
		
		;
		
		private byte value;
		
		DataTypeTags(byte value) {
			this.value=value;
		}

		public byte getValue() {
			return value;
		}
	}

	@Override
	public void encodeBoolean(boolean value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.BOOLEAN, buffer );
		buffer.put( (byte) (value ? 1 : 0));
	}
	
	@Override
	public boolean decodeBoolean(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag ( buffer, DataTypeTags.BOOLEAN, true );
		return buffer.get() > 0;
	}		

	@Override
	public void encodeBooleanArray(boolean[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if( value != null ) {
			encodeTag ( DataTypeTags.BOOLEAN_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for ( boolean item : value) {
				buffer.put( (byte) (item ? 1 : 0));
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public boolean[] decodeBooleanArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if( checkTag ( buffer, DataTypeTags.BOOLEAN_ARRAY )) {
			int len = decodeFloatingLength(buffer);
			boolean[] result = new boolean[len];
			for(int i=0; i<len;i++) {
				result[i] = buffer.get() > 0;
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeDateTime(Date value, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.DATETIME, buffer );
			buffer.putLong( value.getTime() );
		}
		else
			encodeNull ( buffer );
	}
	
	@Override
	public Date decodeDateTime(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if( checkTag ( buffer, DataTypeTags.DATETIME, false ) ) {
			return new Date( buffer.getLong() );
		}
		else
			return null;
	}		

	@Override
	public void encodeDateTimeArray(Date[] value, ResizableByteBuffer buffer) throws  DataTypesCoderException,	BufferUnderflowException {
		if( value != null ) {
			encodeTag ( DataTypeTags.DATETIME_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for ( Date item : value) {
				buffer.putLong(  item.getTime() );
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public Date[] decodeDateTimeArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if( checkTag ( buffer, DataTypeTags.DATETIME_ARRAY )) {
			int len = decodeFloatingLength(buffer);
			Date[] result = new Date[len];
			for(int i=0; i<len;i++) {
				result[i] = new Date ( buffer.getLong() );
			}
			return result;
		}
		else
			return null;
	}	
	
	@Override
	public void encodeByte(byte value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.BYTE, buffer );
		buffer.put(value);
	}			

	@Override
	public byte decodeByte(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag ( buffer, DataTypeTags.BYTE, true );
		return buffer.get();
	}
	
	@Override
	public void encodeUByte(byte value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.UBYTE, buffer );
		buffer.put(value);
	}		

	@Override
	public byte decodeUByte(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		checkTag ( buffer, DataTypeTags.UBYTE, true );
		return buffer.get();
	}		

	@Override
	public void encodeByteArray(byte[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.BYTE_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			buffer.put(value);
		}
		else
			encodeNull(buffer);
	}
	@Override
	public byte[] decodeByteArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.BYTE_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			byte[] result = new byte[len];
			buffer.get(result);
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeUByteArray(byte[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.UBYTE_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			buffer.put(value);
		}
		else
			encodeNull(buffer);
	}	

	@Override
	public byte[] decodeUByteArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.UBYTE_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			byte[] result = new byte[len];
			buffer.get(result);
			return result;
		}
		else
			return null;
	}	
		

	@Override
	public void encodeDouble(double value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.DOUBLE, buffer );
		buffer.putDouble(value);
	}	
		
	@Override
	public double decodeDouble(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.DOUBLE, true);
		return buffer.getDouble();
	}

	@Override
	public void encodeDoubleArray(double[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.DOUBLE_ARRAY, buffer );			
			encodeFloatingLength(value.length, buffer);
			for(double item : value) {
				buffer.putDouble(item);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public double[] decodeDoubleArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.DOUBLE_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			double[] result = new double[len];
			for(int i=0;i<len;i++) {
				result[i]= buffer.getDouble();
			}
			return result;
		}
		else
			return null;
	}

	@Override
	public void encodeFloat(float value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.FLOAT, buffer );
		buffer.putFloat(value);
	}	
	
	@Override
	public float decodeFloat(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.FLOAT, true);
		return buffer.getFloat();
	}
	
	@Override
	public void encodeFloatArray(float[] value, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.FLOAT_ARRAY, buffer );					
			encodeFloatingLength(value.length, buffer);
			for(float item : value) {
				buffer.putFloat(item);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public float[] decodeFloatArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.FLOAT_ARRAY)) {		
			int len = decodeFloatingLength(buffer);
			float[] result = new float[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getFloat();
			}
			return result;
		}
		else
			return null;
	}

	@Override
	public void encodeInt16(short value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.INT16, buffer );
		buffer.putShort(value);
	}	
		

	@Override
	public short decodeInt16(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.INT16, true);
		return buffer.getShort();
	}
	
	@Override
	public void encodeUInt16(short value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.UINT16, buffer );
		buffer.putShort(value);
	}

	
	@Override
	public short decodeUInt16(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.UINT16, true);
		return buffer.getShort();
	}	

	@Override
	public void encodeInt16Array(short[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.INT16_ARRAY, buffer );					
			
			encodeFloatingLength(value.length, buffer);
			for(short item : value) {
				buffer.putShort(item);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public short[] decodeInt16Array(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.INT16_ARRAY)) {		
			
			int len = decodeFloatingLength(buffer);
			short[] result = new short[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getShort();
			}
			return result;
		}
		else
			return null;
	}

	@Override
	public void encodeUInt16Array(short[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.UINT16_ARRAY, buffer );					
			
			encodeFloatingLength(value.length, buffer);
			for(short item : value) {
				buffer.putShort(item);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public short[] decodeUInt16Array(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.UINT16_ARRAY)) {		
			
			int len = decodeFloatingLength(buffer);
			short[] result = new short[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getShort();
			}
			return result;
		}
		else
			return null;
	}	

	@Override
	public void encodeInt32(int value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		encodeTag ( DataTypeTags.INT32, buffer );
		buffer.putInt(value);
	}

	@Override
	public int decodeInt32(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.INT32, true);
		return buffer.getInt();
	}	
	
	@Override
	public void encodeUInt32(int value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.UINT32, buffer );
		buffer.putInt(value);
	}	
	
	@Override
	public int decodeUInt32(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.UINT32, true);
		return buffer.getInt();
	}		
	
	@Override
	public void encodeInt32Array(int[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.INT32_ARRAY, buffer );		
			encodeFloatingLength(value.length, buffer);
			for(int item : value) {
				buffer.putInt(item);
			}
		}
		else
			encodeNull(buffer);
	}
		
	@Override
	public int[] decodeInt32Array(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.INT32_ARRAY)) {
			
			int len = decodeFloatingLength(buffer);
			int[] result = new int[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getInt();
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeUInt32Array(int[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.UINT32_ARRAY, buffer );		
			encodeFloatingLength(value.length, buffer);
			for(int item : value) {
				buffer.putInt(item);
			}
		}
		else
			encodeNull(buffer);
	}	
		

	@Override
	public int[] decodeUInt32Array(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.UINT32_ARRAY)) {
			
			int len = decodeFloatingLength(buffer);
			int[] result = new int[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getInt();
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeInt64(long value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.INT64, buffer );
		buffer.putLong(value);
	}
		
	@Override
	public long decodeInt64(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.INT64, true);
		return buffer.getLong();
	}
	
	@Override
	public void encodeUInt64(long value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.UINT64, buffer );
		buffer.putLong(value);
	}

	@Override
	public long decodeUInt64(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.UINT64, true);
		return buffer.getLong();
	}
	
	@Override
	public void encodeInt64Array(long[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.INT64_ARRAY, buffer );
			
			encodeFloatingLength(value.length, buffer);
			for(long item : value) {
				buffer.putLong(item);
			}
		}
		else
			encodeNull(buffer);
	}

	@Override
	public long[] decodeInt64Array(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.INT64_ARRAY)) {
						
			int len = decodeFloatingLength(buffer);
			long[] result = new long[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getLong();
			}
			return result;
		}
		else
			return null;
	}
	

	@Override
	public void encodeUInt64Array(long[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.UINT64_ARRAY, buffer );
			
			encodeFloatingLength(value.length, buffer);
			for(long item : value) {
				buffer.putLong(item);
			}
		}
		else
			encodeNull(buffer);
	}	
	
	@Override
	public long[] decodeUInt64Array(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.UINT64_ARRAY)) {
			
			int len = decodeFloatingLength(buffer);
			long[] result = new long[len];
			for(int i=0;i<len;i++) {
				result[i]=buffer.getLong();
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeBigInteger(BigInteger value, ResizableByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.BIGINTEGER, buffer );
			encodeStringValue ( value.toString(), STRING_ENCODING, buffer );			
		}
		else
			encodeNull(buffer);
	}

	@Override
	public BigInteger decodeBigInteger(ByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.BIGINTEGER)) {
			return new BigInteger ( decodeStringValue(STRING_ENCODING, buffer) );
		}
		else
			return null;
	}

	@Override
	public void encodeBigIntegerArray(BigInteger[] value,
			ResizableByteBuffer buffer) throws DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.BIGINTEGER_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for(BigInteger item : value) {
				encodeBigInteger(item, buffer);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public BigInteger[] decodeBigIntegerArray(ByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.BIGINTEGER_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			BigInteger[] result = new BigInteger[len];
			for(int i=0; i<len;i++) {
				result[i]=decodeBigInteger(buffer);
			}
			return result;
		}
		else
			return null;
	}

	

	@Override
	public void encodeBigDecimal(BigDecimal value, ResizableByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.BIGDECIMAL, buffer );
			encodeStringValue ( value.toPlainString(), STRING_ENCODING, buffer );			
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public BigDecimal decodeBigDecimal(ByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.BIGDECIMAL)) {
			return new BigDecimal ( decodeStringValue(STRING_ENCODING, buffer) );
		}
		else
			return null;
	}

	@Override
	public void encodeBigDecimalArray(BigDecimal[] value,
			ResizableByteBuffer buffer) throws DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.BIGDECIMAL_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for(BigDecimal item : value) {
				encodeBigDecimal(item, buffer);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public BigDecimal[] decodeBigDecimalArray(ByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.BIGDECIMAL_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			BigDecimal[] result = new BigDecimal[len];
			for(int i=0; i<len;i++) {
				result[i]=decodeBigDecimal(buffer);
			}
			return result;
		}
		else
			return null;
	}
	
	protected void encodeStringValue(String value, String encoding, ResizableByteBuffer buffer) throws DataTypesCoderException {
		try {
			byte[] strArray = value.getBytes(encoding);
			encodeFloatingLength(strArray.length, buffer);
			buffer.put(strArray);
		}
		catch(UnsupportedEncodingException ex) {
			throw new DataTypesCoderException ( ex );
		}
	}	
	
	private String decodeStringValue(String encoding, ByteBuffer buffer)
			throws DataTypesCoderException {
		try {
			int len = decodeFloatingLength(buffer);
			//if(buffer.hasArray()) {
			//		String result = new String(buffer.array(), buffer.position(), len, encoding);
			//		buffer.position(buffer.position()+len);
			//		return result;
			//}
			//else {
				byte[] stringArray = new byte[len];
				buffer.get(stringArray);
				return new String(stringArray, encoding);
			//}
		}
		catch(UnsupportedEncodingException ex) {
			throw new DataTypesCoderException ( ex );
		}					
	}	
	
	@Override
	public void encodeString(String value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.STRING, buffer );
			encodeStringValue(value, STRING_ENCODING, buffer);
		}
		else
			encodeNull(buffer);
	}

	@Override
	public String decodeString(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.STRING)) {
			return decodeStringValue(STRING_ENCODING, buffer);
		}
		else
			return null;
	}

	@Override
	public void encodeStringArray(String[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.STRING_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for(String item : value) {
				encodeString(item, buffer);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public String[] decodeStringArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.STRING_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			String[] result = new String[len];
			for(int i=0; i<len;i++) {
				result[i]=decodeString(buffer);
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeWString(String value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.WSTRING, buffer );
			buffer.put( UnicodeEncodingTypes.UCS_2.getValue() );
			encodeStringValue ( value, UNICODE_STRING_ENCODING, buffer );
		}
		else
			encodeNull(buffer);
	}
	

	@Override
	public String decodeWString(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.WSTRING)) {
			byte unicodeType = buffer.get();
			if( unicodeType != UnicodeEncodingTypes.UCS_2.getValue() )
				throw new DataTypesCoderException("Invalid unicode encoding type ID : "+unicodeType) ;
			return decodeStringValue ( UNICODE_STRING_ENCODING, buffer );				
		}
		else
			return null;
	}
	
	@Override
	public void encodeWStringArray(String[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.WSTRING_ARRAY, buffer );
		
			encodeFloatingLength(value.length, buffer);
			for ( String item  : value) {
				encodeWString(item, buffer);
			}
		}
		else
			encodeNull(buffer);
	}	

	@Override
	public String[] decodeWStringArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.WSTRING_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			String[] result = new String[len];
			for(int i=0; i<len;i++) {
				result[i]=decodeWString(buffer);
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeChar(char value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.CHAR, buffer );
		buffer.put( (byte)value);
	}
		
	@Override
	public char decodeChar(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.CHAR, true);
		return (char)buffer.get();
	}

	@Override
	public void encodeCharArray(char[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value != null) {
			encodeTag ( DataTypeTags.CHAR_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for(char ch : value) {
				buffer.putInt( (int)ch);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public char[] decodeCharArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.CHAR_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			char[] result = new char[len];
			for(int i=0; i<len;i++) {
				result[i] = (char)buffer.getInt();
			}
			return result;
		}
		else
			return null;
	}
	
	@Override
	public void encodeWChar(char value, ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.WCHAR, buffer );
		buffer.putChar(value);
	}
	
	@Override
	public char decodeWChar(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		checkTag(buffer, DataTypeTags.WCHAR, true);
		return buffer.getChar();
	}

	@Override
	public void encodeWCharArray(char[] value, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		if(value!=null) {
			encodeTag ( DataTypeTags.WCHAR_ARRAY, buffer );
			encodeFloatingLength(value.length, buffer);
			for ( char item : value ) {
				buffer.putChar(item);
			}
		}
		else
			encodeNull(buffer);
	}
	
	@Override
	public char[] decodeWCharArray(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.WCHAR_ARRAY)) {
			int len = decodeFloatingLength(buffer);
			char[] result = new char[len];
			for(int i=0; i<len;i++) {
				result[i]=buffer.getChar();
			}
			return result;
		}
		else
			return null;
	}		
	
	@Override
	public void encodeObjectArray(int arraySize, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.OBJECT_ARRAY, buffer );
		encodeFloatingLength(arraySize, buffer);
	}
	
	@Override
	public int decodeObjectArray(ByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.OBJECT_ARRAY)) {
			return decodeFloatingLength(buffer);
		}
		else
			return 0;		
	}
	
	@Override
	public int encodeObjectListBegin(ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.OBJECT_LIST, buffer );
		int beginPos = buffer.getBuffer().position();
		buffer.putInt(0); // reserving space for size
		return beginPos;		
	}
	
	@Override
	public int decodeObjectListBegin(ByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		if( checkTag(buffer, DataTypeTags.OBJECT_LIST) ){
			return buffer.getInt();
		}
		else
			return 0;
	}

	@Override
	public void encodeObjectListEnd(int beginPos, int count, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		// encodeTag ( DataTypeTags.OBJECT_LIST_END, buffer );
		buffer.putInt( beginPos, count );		
	}
	
	@Override
	public void decodeObjectListEnd(ByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
	}
	
	@Override
	public void encodeObjectMap(int mapSize, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.OBJECT_MAP, buffer );
		encodeFloatingLength(mapSize, buffer);
	}
	
	@Override
	public int decodeObjectMap(ByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		if(checkTag(buffer, DataTypeTags.OBJECT_MAP)) {
			return decodeFloatingLength(buffer);
		}
		else
			return 0;		
	}	

	@Override
	public void encodeFloatingLength(int length, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
        
        if (length < 0) {
            throw new DataTypesCoderException("The specified length to encode < 0 "+length) ;
        }
        else 
        if (length <= 0x7F) {
            buffer.put((byte) length);
        }
        else 
        if (length <= 0xFF) {
        	buffer.put((byte) 0x81);
        	buffer.put((byte) length);
        }
        else 
        if (length <= 0xFFFF) {
        	buffer.put((byte) 0x82);
        	buffer.put((byte) (length >> 8));
        	buffer.put((byte) length);
        }
        else 
        if (length < 0xFFFFFF) {
        	buffer.put((byte) 0x83);
        	buffer.put((byte) (length >> 16));
        	buffer.put((byte) (length >> 8));
        	buffer.put((byte) length);
        }
        else
        if (length < Integer.MAX_VALUE) {
        	buffer.put((byte) 0x84);
        	buffer.put((byte) (length >> 24));
        	buffer.put((byte) (length >> 16));
        	buffer.put((byte) (length >> 8));
        	buffer.put((byte) length);
        }
        else
        	throw new DataTypesCoderException("The specified length too big"+length);
	}
	
	@Override
	public int decodeFloatingLength(ByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		int result = 0 ;        
        int bt = buffer.get();
        if(bt<0)
        	bt = 256 + bt;

        if (bt < 128 ) {
            result = bt ;
        }
        else {
            
            for (int i = bt - 128; i > 0 ; i--) {
                int fBt = buffer.get();
                if(fBt<0)
                	fBt = 256 + fBt;
                
                result = result << 8 ;
                result = result | fBt ;
            }
        }
        return result;
	}	

	@Override
	public void encodeStructureDataTypeBegin(ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.STRUCTURE_TYPE_BEGIN, buffer );
	}
	
	@Override
	public boolean decodeStructureDataTypeBegin(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		return checkTag ( buffer, DataTypeTags.STRUCTURE_TYPE_BEGIN );
	}
	
	@Override
	public void encodeStructureDataTypeEnd(ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.STRUCTURE_TYPE_END, buffer );
	}
	
	@Override
	public void decodeStructureDataTypeEnd(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		// checkTag ( buffer, DataTypeTags.STRUCTURE_TYPE_END, true );
		
		int tag = decodeTag ( buffer );
		while ( tag == DataTypeTags.NAMED_DATA_PART.getValue() ) {
			// decodeStringValue ( STRING_ENCODING, buffer );
			// Skip part Id
			buffer.getInt();
			
			int partLen = buffer.getInt();
			// Skip the current part
			buffer.position ( buffer.position() + partLen );
			tag = decodeTag ( buffer );
		}
		
		if(tag != DataTypeTags.STRUCTURE_TYPE_END.getValue())
			throw new DataTypesCoderException("Incorrect data type received. Found tag: "+tag+". Expected: "+DataTypeTags.STRUCTURE_TYPE_END.getValue());
	}
	
	@Override
	public int encodeNamedDataPartBegin ( String structurePartName, ResizableByteBuffer buffer )
		throws  DataTypesCoderException, BufferUnderflowException {
		
		encodeTag ( DataTypeTags.NAMED_DATA_PART, buffer );
		// encodeStringValue ( structurePartName, STRING_ENCODING, buffer );
		buffer.putInt(this.grammar.registerGrammarElement(structurePartName));
		
		int beginPos = buffer.getBuffer().position();
		buffer.putInt(0); // reserving space for size
		return beginPos;
	}
	
	@Override
	public int decodeNamedDataPartBegin ( String structurePartName, ByteBuffer buffer)
		throws  DataTypesCoderException, BufferUnderflowException {
		int result = 0;
		int currentPos = buffer.position();
		boolean tryingToFindPart = true;
		while(tryingToFindPart) {
			int tag = decodeTag ( buffer );
			if ( tag == DataTypeTags.NAMED_DATA_PART.getValue() ) {
				// String partName = decodeStringValue ( STRING_ENCODING, buffer );
				int partId = buffer.getInt();
				String partName = this.grammar.findGrammarElement(partId);
				result = buffer.position();
				int partLen = buffer.getInt();
				if(partName.equals(structurePartName)) {
					tryingToFindPart = false;
				}
				else {
					result = 0;
					// Skip the current part
					buffer.position ( buffer.position() + partLen );				
				}
			}
			else {
				buffer.position(currentPos);
				tryingToFindPart = false;
			}
		}
		if(result == 0)
			throw new DataTypesCoderException("Unable to found the structure part '"+structurePartName+"' in the read stream");
		return result;
	}

	@Override
	public void encodeNamedDataPartEnd ( int beginPos, ResizableByteBuffer buffer )
		throws  DataTypesCoderException, BufferUnderflowException {
		int size = buffer.getBuffer().position()  - ( beginPos + 4 ) ;
		buffer.putInt( beginPos, size );
	}
	
	@Override
	public void decodeNamedDataPartEnd ( int beginPos, ByteBuffer buffer ) throws  DataTypesCoderException, BufferUnderflowException {
		// Skipping additional data in the part
		int partLen = buffer.getInt( beginPos );
		int estiminatedDataLen = ( beginPos + 4 + partLen ) - buffer.position() ;
		if ( estiminatedDataLen > 0 ) {
			buffer.position ( buffer.position() + estiminatedDataLen );
		}
	}
	
	@Override
	public void encodeExceptionDataTypeBegin(ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.EXCEPTION_TYPE_BEGIN, buffer );
	}
	
	@Override
	public boolean decodeExceptionDataTypeBegin(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		return checkTag ( buffer, DataTypeTags.EXCEPTION_TYPE_BEGIN );
	}
	
	@Override
	public void encodeExceptionDataTypeEnd(ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.EXCEPTION_TYPE_END, buffer );
	}
	
	@Override
	public void decodeExceptionDataTypeEnd(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		checkTag ( buffer, DataTypeTags.EXCEPTION_TYPE_END, true );
	}	
	
	@Override
	public int encodeChoiceDataTypeBegin( ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.CHOICE_TYPE, buffer );
		int beginPos = buffer.getBuffer().position();
		buffer.putInt(0); // reserving space for size
		return beginPos;
	}
	
	@Override
	public void encodeChoiceDataTypeEnd(int beginPos, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		int size = buffer.getBuffer().position()  - ( beginPos + 4 ) ;
		buffer.putInt( beginPos, size );
	}	
	
	@Override
	public void encodeChoiceSelectedField( String selectedField, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		// encodeStringValue ( selectedField, STRING_ENCODING, buffer );
		buffer.putInt(this.grammar.registerGrammarElement(selectedField));
	}
	
	@Override
	public String decodeChoiceSelectedField( ByteBuffer buffer ) throws  DataTypesCoderException, BufferUnderflowException {
		// return decodeStringValue ( STRING_ENCODING, buffer );
		return this.grammar.findGrammarElement(buffer.getInt());
	}
	
	@Override
	public int decodeChoiceDataTypeBegin(ByteBuffer buffer) throws DataTypesCoderException, BufferUnderflowException {		
		checkTag ( buffer, DataTypeTags.CHOICE_TYPE, true );
		int pos = buffer.position();
		buffer.getInt(); // skipping len
		return pos;
	}
	
	@Override
	public void decodeChoiceDataTypeEnd ( int beginPos, ByteBuffer buffer ) throws  DataTypesCoderException, BufferUnderflowException {
		// Skipping additional data in the part
		int partLen = buffer.getInt( beginPos );
		int estiminatedDataLen = ( beginPos + 4 + partLen ) - buffer.position() ;
		if ( estiminatedDataLen > 0 ) {
			buffer.position ( buffer.position() + estiminatedDataLen );
		}
	}	
	
	@Override
	public void encodeEnumDataType(int selectedIndex, ResizableByteBuffer buffer)
			throws  DataTypesCoderException,
			BufferUnderflowException {
		encodeTag ( DataTypeTags.ENUM_TYPE, buffer );
		buffer.putInt(selectedIndex);
	}
	
	@Override
	public int decodeEnumDataType(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {		
		if(!checkTag ( buffer, DataTypeTags.ENUM_TYPE )) {
			return 0;
		}
		else {
			return buffer.getInt();
		}
	}	

	@Override
	public int encodeEncryptedDataTypeBegin(ResizableByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.ENCRYPTED_DATA, buffer );
		int beginPos = buffer.getBuffer().position();
		buffer.putInt(0); // reserving space for size
		return beginPos;
	}
	
	@Override
	public int decodeEncryptedDataTypeBegin(ByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		checkTag ( buffer, DataTypeTags.ENCRYPTED_DATA, true );
		return buffer.getInt();
	}	
	
	@Override
	public void encodeEncryptedDataTypeEnd( int beginPos, ResizableByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
		int size = buffer.getBuffer().position()  - ( beginPos + 4 ) ;
		buffer.putInt(beginPos, size);
	}
	
	@Override
	public void decodeEncryptedDataTypeEnd(ByteBuffer buffer)
			throws DataTypesCoderException, BufferUnderflowException {
	}	

	@Override
	public void encodeNull(ResizableByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.NULL, buffer );
	}
	
	@Override
	public boolean decodeNull(ByteBuffer buffer) throws 
			DataTypesCoderException, BufferUnderflowException {
		int curPos = buffer.position();
		if(decodeTag(buffer) != DataTypeTags.NULL.getValue()) {
			buffer.position(curPos);
			return false;
		}
		else
			return true;
	}
	@Override
	public void encodeObjectType( String objectType, ResizableByteBuffer buffer ) throws DataTypesCoderException, BufferUnderflowException {
		encodeTag ( DataTypeTags.OBJECT_TYPE, buffer );
		// encodeStringValue ( objectType, STRING_ENCODING, buffer );
		buffer.putInt(this.grammar.registerGrammarElement(objectType));
	}
	
	@Override
	public String decodeObjectType( ByteBuffer buffer ) 
		throws DataTypesCoderException, BufferUnderflowException {
		checkTag ( buffer, DataTypeTags.OBJECT_TYPE, true );
		// return decodeStringValue ( STRING_ENCODING, buffer );		
		return this.grammar.findGrammarElement(buffer.getInt());
	}	
	
	@Override
	public void encodeGrammar(ResizableByteBuffer buffer)
		throws DataTypesCoderException, BufferUnderflowException {
		try {
			this.grammar.serialize(this, null, buffer);
		}
		catch(SmartBusSystemException ex) {
			throw new DataTypesCoderException(ex);
		}
	}
	
	@Override
	public void decodeGrammar(ByteBuffer buffer) throws DataTypesCoderException, BufferUnderflowException {
		try {		
			this.grammar.deserialize(this, null, buffer);
		}
		catch(SmartBusSystemException ex) {
			throw new DataTypesCoderException(ex);
		}			
	}
	

	private void encodeTag(DataTypeTags tag, ResizableByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException{
		buffer.put(tag.getValue());
	}
	
	public byte decodeTag(ByteBuffer buffer) throws  DataTypesCoderException, BufferUnderflowException {
		return buffer.get();
	}
		
	private boolean checkTag(ByteBuffer buffer, DataTypeTags type) throws  DataTypesCoderException, BufferUnderflowException {
		return checkTag(buffer, type, false);
	}
	
	
	private boolean checkTag(ByteBuffer buffer, DataTypeTags type, boolean cannotBeNull) throws  DataTypesCoderException, BufferUnderflowException {
		byte tag = decodeTag(buffer);
		if( tag!= type.getValue() ) {
			if( cannotBeNull || tag != DataTypeTags.NULL.getValue() )
				throw new DataTypesCoderException("Incorrect data type received. Found tag: "+tag+". Expected: "+type.getValue());
			return false;
		}
		else
			return true;
	}
	
	@Override
	public void reset() {
		this.grammar.reset();
	}	
}
